package config;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

/**
 * Набор статический функций для парсинга .ini файлов Возможно следует
 * преобразовать в нормальный класс
 * 
 * @author Last G
 * 
 */
public class INIParser
{
	/**
	 * Парсит строку с кофигурациями в формате .ini файла
	 * 
	 * Поддерживаются .ini файлы с секциями Символы комментария ; и #
	 * 
	 * Одноимённые секции в разный местах файла - сливаются
	 * 
	 *@param config
	 *            строка с .ini данными
	 *@return хеш хешей - первый ключ имя секции("" - пустая строка - имя
	 *         глобальной секции), второй ключ - имя параметра
	 * 
	 * @throws ParserException
	 *             плюётся исключениями :)
	 * 
	 */
	public static Map<String, Map<String, String>> Parse(String config)
			throws ParserException
	{
		// TODO реализовать настройку для того, что бы был выбор кто
		// отрабатывает исключение,
		// ибо при желание можно просто игнорировать битые строки в конфигах
		Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
		String[] strs = config.split("\r|\n");
		String section = "";
		result.put(section, new HashMap<String, String>());
		for (String i : strs)
		{
			if (!i.equals(""))
			{
				section = ParseTo(i, result, section);
			}
		}

		return result;
	}

	/**
	 * Парсит укаанный файл с кофигурациями в формате .ini
	 * 
	 * Поддерживаются .ini файлы с секциями Символы комментария ; и #
	 * 
	 * Одноимённые секции в разный местах файла - сливаются
	 * 
	 *@param config
	 *            .ini файл для парсинга
	 *@return хешь хешей - первый ключ имя секции("" - пустая строка - имя
	 *         глобальной секции), второй ключ - имя параметра
	 * 
	 * @throws ParserException
	 *             плюётся )
	 * 
	 */
	public static Map<String, Map<String, String>> Parse(File config)
			throws ParserException
	{
		// TODO смотри от функции выше :)

		Scanner in;
		Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();

		String section = "";
		result.put(section, new HashMap<String, String>());

		try
		{
			in = new Scanner(config);
		} catch (FileNotFoundException e)
		{
			throw new ParserException(e.toString());
		}

		while (in.hasNextLine())
		{
			String str = in.nextLine();
			section = ParseTo(str, result, section);
		}
		in.close();

		return result;
	}

	/**
	 * Внутренняя функция для парсинга одной строги и добавления к текущей
	 * конфигурации.
	 * 
	 * Принимает строку и парсит её в текущей конфигурации строки начинающиеся с
	 * ; и # ничего не меняют
	 * 
	 * @param str
	 *            строка для парсинга
	 * @param config
	 *            текущая конфигураци
	 * @param section
	 *            имя текущей секции
	 * @return новая секция(равна section, если строка не являлось строкой
	 *         обозначения новой секции)
	 * 
	 * @throws ParserException
	 *             выбрасывает исключение с описанием что случилось не так в
	 *             астомате
	 */
	private static String ParseTo(String str,
			Map<String, Map<String, String>> config, String section)
			throws ParserException
	{
		// ВСЕМ БОЯТЬСЯ - ХАРДКОДЕД АВТОМАТ!!!

		// 0 - : начальное, конечное

		// 1 - распознаём имя
		// 2 - начальные пробелы в значение
		// 3 - распознаём значение : конечное

		// 6 - комментарий : конечное

		// 11 - распознаём распознаём имя секции
		// 12 - случилась ] : коненое

		// 28 - корзина

		String res = section;
		int state = 0;
		int[] endStates = new int[] { 0, 3, 6, 12 }; // должно быть
		// отсортировано :)
		int fi = 0; // индекс начала запоминаемого слова
		int li = 0; // индекс конца запоминаемого слова
		String name = null;
		String value = null;

		StringBuilder error = new StringBuilder();

		// Основной цикл для автомата - по входному сообщению
		for (int i = 0; i < str.length(); ++i)
		{
			switch (state)
			{
			case 0:
				switch (str.charAt(i))
				{
				case ' ':
					break;
				case '	':
					break;
				case ';':
					state = 6;
					break;
				case '#':
					state = 6;
					break;
				case '[':
					state = 11;
					fi = i + 1;
					li = i + 1;
					break;
				default:
					state = 1;
					fi = i;
					li = i + 1;
					break;
				}
				break;
			case 1:
				switch (str.charAt(i))
				{
				case ' ':
					break;
				case '	':
					break;
				case '=':
					state = 2;
					name = str.substring(fi, li);
					break;
				default:
					// state = 2;
					li = i + 1;
					break;
				}
				break;
			case 2:
				switch (str.charAt(i))
				{
				case ' ':
					break;
				case '	':
					break;
				default:
					state = 3;
					fi = i;
					li = i + 1;
					break;
				}
				break;
			case 3:
				switch (str.charAt(i))
				{
				case ' ':
					break;
				case '	':
					break;
				default:
					state = 3;
					li = i + 1;
					break;
				}
				break;
			case 6:
				break;

			case 11:
				switch (str.charAt(i))
				{
				case ']':
					state = 12;
					break;
				default:
					state = 11;
					li = i + 1;
					break;
				}
				break;
			case 12:
				// state = 28;
				break;

			case 28:
				state = 28;
				break;
			default:
				state = 28;
				error.append("Automaton in wrong state;");
				break;
			}
		}

		// TODO подробные описания ошибок при выходе не из финального состояния
		if (java.util.Arrays.binarySearch(endStates, state) < 0)
		{
			error.append("Not in final state;");
			throw new ParserException(error.toString());
		}
		if (error.length() != 0)
		{
			throw new ParserException(error.toString());
		}
		switch (state)
		{
		case 3:
			value = str.substring(fi, li);
			config.get(section).put(name, value);
			break;
		case 12:
			res = str.substring(fi, li);
			config.put(res, new HashMap<String, String>());
			break;
		default:
			break;
		}
		return res;
	}
}
